﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Grinder.Renamer;
using Grinder.Properties;
using System.ComponentModel;
using Grinder.Settings;
using System.Linq;

namespace Grinder.Forms
{
    public sealed partial class MainForm : Form
    {
        private readonly string _settingsFile = new FileInfo(Application.ExecutablePath).Name + ".ini";

        private OpenFileDialog _openFileDialog;
        private FolderBrowserDialog _folderBrowserDialog;

        private TemplateEditorForm _templateEditorForm;
        private RangeForm _rangeForm;
        private FindReplaceForm _findReplaceForm;

        private FileRenamer _renamer;
        private ToolTip _toolTipError;
        private BackgroundWorker _processWorker;
        private BackgroundWorker _addWorker;

        public MainForm()
        {
            Text = AssemblyInfo.AssemblyProduct;
            InitializeComponent();
            InitializeDialogs();            
            InitializeBackgroundWorkers();
            InitializeControls();
            LoadSettings();
            
            _renamer = new FileRenamer();
        }

        #region Save and Load Settings
        private void LoadSettings()
        {
            var inifile = new IniSettings(_settingsFile);

            string[] templates = null;

            try
            {
                templates = inifile.GetValue("MainForm", "SavedTemplates").Split(new[] { "|    |" }, StringSplitOptions.RemoveEmptyEntries);
                textReplaceInvalidWith.Text = inifile.GetValue("MainForm", "ReplaceInvalidWith");
                numCounterStart.Value = inifile.GetIntValue("MainForm", "CounterStart");
                numCounterStep.Value = inifile.GetIntValue("MainForm", "CounterStep");
                checkOverwrite.Checked = inifile.GetBooleanValue("MainForm", "Overwrite");
                checkRecurse.Checked = inifile.GetBooleanValue("MainForm", "Recurse");
                comboOperation.SelectedItem = inifile.GetEnumValue<ProcessingMode>("MainForm", "Mode");
            }
            catch { }

            if (templates != null)
                _templateEditorForm.Templates = templates;
        }

        private void SaveSettings()
        {
            using (var inifile = new IniSettings(_settingsFile))
            {
                string templates = string.Join("|    |", _templateEditorForm.Templates.ToArray());
                inifile.SetValue("MainForm", "SavedTemplates", templates);

                inifile.SetValue("MainForm", "CounterStart", numCounterStart.Value);
                inifile.SetValue("MainForm", "CounterStep", numCounterStep.Value);
                inifile.SetValue("MainForm", "ReplaceInvalidWith", textReplaceInvalidWith.Text);
                inifile.SetValue("MainForm", "Overwrite", checkOverwrite.Checked);
                inifile.SetValue("MainForm", "Recurse", checkRecurse.Checked);
                inifile.SetValue("MainForm", "Mode", comboOperation.SelectedItem);
            }
        }
        #endregion

        #region Initialize Stuff
        private void InitializeControls()
        {
            foreach (var op in Enum.GetValues(typeof(ProcessingMode)))
                comboOperation.Items.Add(op);

            comboOperation.SelectedIndex = 0;

            btnCancel.Enabled = false;
            panelAdvanced.Visible = false;
            panelProgress.Visible = false;

            _toolTipError = new ToolTip
                                {
                                    ToolTipIcon = ToolTipIcon.Error,
                                    ToolTipTitle = "Error",
                                    UseAnimation = true,
                                    AutomaticDelay = 2000
                                };
        }

        private void InitializeBackgroundWorkers()
        {
            _processWorker = new BackgroundWorker
                          {
                              WorkerReportsProgress = true,
                              WorkerSupportsCancellation = true
                          };
            _processWorker.DoWork += ProcessWorkerDoWork;
            _processWorker.RunWorkerCompleted += ProcessWorkerCompleted;
            _processWorker.ProgressChanged += ProcessWorkerProgressChanged;

            _addWorker = new BackgroundWorker
            {
                WorkerReportsProgress = true,
                WorkerSupportsCancellation = true
            };

            _addWorker.DoWork += AddWorkerDoWork;
            _addWorker.ProgressChanged += AddWorkerProgressChanged;
            _addWorker.RunWorkerCompleted += AddWorkerCompleted;
        }

        private void InitializeDialogs()
        {
            _folderBrowserDialog = new FolderBrowserDialog { ShowNewFolderButton = false };
            _templateEditorForm = new TemplateEditorForm(PluginManager.RenamePlugins);
            _rangeForm = new RangeForm();
            _findReplaceForm = new FindReplaceForm();

            _openFileDialog = new OpenFileDialog { Multiselect = true };

            var sb = new StringBuilder();

            foreach (var plugin in PluginManager.RenamePlugins)
            {
                string joinedExtensions = string.Join(";", plugin.Extensions.ToArray());

                sb.Append(String.Format("|{0} ({1})|{1}", plugin.Name, joinedExtensions.ToLower()));
            }

            _openFileDialog.Filter = sb.ToString().Trim('|');
        }
        #endregion

        #region Event Handlers
        
        private void btnAdvanced_Click(object sender, EventArgs e)
        {
            panelAdvanced.Visible = !panelAdvanced.Visible;

            btnAdvanced.Image = panelAdvanced.Visible ?
                Resources.bullet_arrow_up : Resources.bullet_arrow_down;

            tabMain.Refresh();
        }

        private void btnTemplateEditor_Click(object sender, EventArgs e)
        {
            if (_templateEditorForm.ShowDialog(textTemplate.Text) == DialogResult.OK)
            {
                textTemplate.Text = _templateEditorForm.Template;
                UpdatePreview();
            }
        }

        private void comboOperation_SelectedIndexChanged(object sender, EventArgs e)
        {
            var operation = (ProcessingMode)comboOperation.SelectedItem;

            if (operation == ProcessingMode.Rename)
            {
                textDestination.Enabled = false;
                btnDestination.Enabled = false;
                checkOverwrite.Enabled = false;
            }
            else
            {
                textDestination.Enabled = true;
                btnDestination.Enabled = true;
                checkOverwrite.Enabled = true;
            }

            UpdatePreview();
        }

        private void btnReplace_Click(object sender, EventArgs e)
        {
            _findReplaceForm.ShowDialog();
            UpdatePreview();
        }

        private void btnAddFiles_Click(object sender, EventArgs e)
        {
            if (_openFileDialog.ShowDialog() == DialogResult.OK)
                StartFileAddThread(_openFileDialog.FileNames);
        }

        private void textTemplate_TextChanged(object sender, EventArgs e)
        {
            UpdatePreview();
        }

        private void btnRange_Click(object sender, EventArgs e)
        {
            if (listPreview.Items.Count > 0)
            {
                FileInfo fileInfo;

                if (listPreview.SelectedItems.Count > 0)
                    fileInfo = listPreview.SelectedItems[0].Tag as FileInfo;
                else
                    fileInfo = listPreview.Items[0].Tag as FileInfo;

                if (fileInfo != null && _rangeForm.ShowDialog(fileInfo.Name) == DialogResult.OK)
                {
                    textTemplate.Text = textTemplate.Text.Insert(textTemplate.SelectionStart,
                                                                 _rangeForm.SelectedRange);
                    UpdatePreview();
                }
            }
            else
            {
                ShowErrorMessage(listPreview, "Please add some files");
            }
        }

        private void btnAddFolder_Click(object sender, EventArgs e)
        {
            _folderBrowserDialog.ShowNewFolderButton = false;

            if (_folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                var dirInfo = new DirectoryInfo(_folderBrowserDialog.SelectedPath);

                SearchOption searchOption = (checkRecurse.Checked)
                                                ? SearchOption.AllDirectories
                                                : SearchOption.TopDirectoryOnly;

                StartFileAddThread(dirInfo.GetFiles("*", searchOption));
            }
        }

        private void btnMoveUp_Click(object sender, EventArgs e)
        {
            listPreview.MoveSelectedUp();

            UpdatePreview();
        }

        private void btnMoveDown_Click(object sender, EventArgs e)
        {
            listPreview.MoveSelectedDown();

            UpdatePreview();
        }

        private void btnRemoveFiles_Click(object sender, EventArgs e)
        {
            listPreview.RemoveSelected();

            if (listPreview.Items.Count == 0)
                _renamer.ClearCache();

            UpdatePreview();
        }

        private void ShowErrorMessage(IWin32Window control, string message)
        {
            _toolTipError.Show(message, control, 2000);
        }

        private void btnRename_Click(object sender, EventArgs e)
        {
            var operation = (ProcessingMode)comboOperation.SelectedItem;

            if (string.IsNullOrEmpty(textTemplate.Text))
            {
                ShowErrorMessage(textTemplate, "Please specify file name template");
                return;
            }

            if (operation != ProcessingMode.Rename && string.IsNullOrEmpty(textDestination.Text))
            {
                ShowErrorMessage(textDestination, "Please specify destination folder");
                return;
            }

            if (listPreview.Items.Count == 0)
            {
                ShowErrorMessage(listPreview, "Please add some files");
                return;
            }

            UpdatePreview();

            var fileList = new List<ProcessFileParams>();

            foreach (ListViewItem item in listPreview.Items)
            {
                fileList.Add(new ProcessFileParams(
                    item.Tag as FileInfo,
                    item.SubItems[2].Text,
                    operation,
                    textDestination.Text,
                    checkOverwrite.Checked));
            }

            var review = new OverviewForm(fileList.Count, textDestination.Text, operation, checkOverwrite.Checked);

            if (review.ShowDialog() == DialogResult.OK)
            {
                ClearStatus();
                ToggleControls();
                progressProcess.Maximum = fileList.Count;
                panelProgress.Visible = true;
                _processWorker.RunWorkerAsync(fileList);
            }
        }

        private void ClearStatus()
        {
            foreach (ListViewItem item in listPreview.Items)
            {
                var subItem = item.SubItems[3];
                if (subItem != null)
                    subItem.Text = "";
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            if (_processWorker.IsBusy)
                _processWorker.CancelAsync();

            if (_addWorker.IsBusy)
                _addWorker.CancelAsync();
        }

        private void numField_ValueChanged(object sender, EventArgs e)
        {
            UpdatePreview();
        }

        private void btnQuit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new AboutForm().ShowDialog();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ((_addWorker.IsBusy || _processWorker.IsBusy) &&
                MessageBox.Show("Operation in progress. Close anyway?", "Operation in progress", MessageBoxButtons.YesNo,
                                MessageBoxIcon.Question) == DialogResult.No)
            {
                e.Cancel = true;
                return;
            }

            SaveSettings();
        }

        private void textReplaceInvalidWith_KeyPress(object sender, KeyPressEventArgs e)
        {
            foreach (var s in RenamerConstants.InvalidChars)
                if (s == e.KeyChar.ToString())
                {
                    e.Handled = true;
                    return;
                }
        }

        private void textReplaceInvalidWith_TextChanged(object sender, EventArgs e)
        {
            UpdatePreview();
        }

        private void listPreview_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effect = DragDropEffects.Copy;
        }

        private void listPreview_DragDrop(object sender, DragEventArgs e)
        {
            var dropArray = (string[])e.Data.GetData(DataFormats.FileDrop);

            if (dropArray == null) return;

            var files = new List<object>();

            foreach (string file in dropArray)
            {
                if (File.Exists(file))
                    files.Add(new FileInfo(file));
                else if (Directory.Exists(file))
                    files.AddRange(
                        new DirectoryInfo(file).GetFiles("*", checkRecurse.Checked ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
                        );
            }

            StartFileAddThread(files);
        }

        private void btnRefreshPreview_Click(object sender, EventArgs e)
        {
            UpdatePreview();
        }

        private void btnDestination_Click(object sender, EventArgs e)
        {
            _folderBrowserDialog.ShowNewFolderButton = true;
            if (_folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                textDestination.Text = _folderBrowserDialog.SelectedPath;
            }
        }

        private void helpContentsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start(Constants.HelpUrl);
        }

        private void websiteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start(Constants.WebsiteUrl);
        }

        #endregion

        #region Helpers
        private void StartFileAddThread(IEnumerable<object> files)
        {
            if (files.Count() == 0) return;

            ToggleControls();
            _addWorker.RunWorkerAsync(files);
        }

        private void ToggleControls()
        {
            btnProcess.Enabled = !btnProcess.Enabled;
            btnCancel.Enabled = !btnCancel.Enabled;
            btnAddFiles.Enabled = !btnAddFiles.Enabled;
            btnAddFolder.Enabled = !btnAddFolder.Enabled;
            btnMoveUp.Enabled = !btnMoveUp.Enabled;
            btnMoveDown.Enabled = !btnMoveDown.Enabled;
            btnRemoveFiles.Enabled = !btnRemoveFiles.Enabled;
            btnRefreshPreview.Enabled = !btnRefreshPreview.Enabled;
        }

        private void UpdatePreview()
        {
            if (!string.IsNullOrEmpty(textTemplate.Text) && listPreview.Items.Count > 0)
            {                
                _renamer.FindReplaceItems = _findReplaceForm.FindReplaceItems;
                _renamer.ReplaceInvalidCharsWith = textReplaceInvalidWith.Text;

                var counter = (int)numCounterStart.Value;

                foreach (ListViewItem item in listPreview.Items)
                {
                    var fileInfo = item.Tag as FileInfo;

                    if (fileInfo != null)
                    {
                        string newFileName = _renamer.GetNewFilename(textTemplate.Text, fileInfo, counter);

                        item.SubItems[2] = new ListViewItem.ListViewSubItem(item, newFileName);

                        if (fileInfo.Name == newFileName
                            && (ProcessingMode)comboOperation.SelectedItem == ProcessingMode.Rename)
                        {
                            item.BackColor = Color.MistyRose;
                        }
                        else
                        {
                            item.BackColor = SystemColors.Window;
                        }
                    }

                    counter += (int)numCounterStep.Value;
                }
            }
        }
        #endregion

        #region Background Worker Event Handlers
        private void ProcessWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressProcess.Value = e.ProgressPercentage + 1;

            var logMessage = e.UserState as LogMessage;

            if (logMessage == null) return;

            int itemIndex = e.ProgressPercentage;
            ListViewItem item = listPreview.Items[itemIndex];

            item.SubItems[3].Text = logMessage.Message;

            switch (logMessage.Type)
            {
                case LogType.Error:
                    item.BackColor = Color.MistyRose;
                    break;
                case LogType.Warning:
                    item.BackColor = Color.LightGoldenrodYellow;
                    break;
                case LogType.Ok:
                    item.BackColor = SystemColors.Window;
                    break;
            }

            item.EnsureVisible();
        }

        private static void ProcessWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            var worker = sender as BackgroundWorker;
            if (worker == null) return;

            var files = (List<ProcessFileParams>)e.Argument;

            for (int i = 0; i < files.Count; i++)
            {
                if (worker.CancellationPending)
                {
                    worker.ReportProgress(i, new LogMessage { Message = "Processing cancelled", Type = LogType.Warning });
                    return;
                }

                ProcessFileParams prParams = files[i];

                try
                {
                    FileRenamer.ProcessFile(prParams);
                }
                catch (Exception ex)
                {
                    worker.ReportProgress(i, new LogMessage { Message = ex.Message, Type = LogType.Error });
                    continue;
                }

                worker.ReportProgress(i, new LogMessage { Message = "OK", Type = LogType.Ok });
            }
        }

        private void ProcessWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            panelProgress.Visible = false;
            progressProcess.Value = 0;
            ToggleControls();
        }

        private void AddWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            var fileInfo = e.UserState as FileInfo;

            if (fileInfo == null) return;

            if (listPreview.Items.ContainsKey(fileInfo.FullName)) return;

            var item = listPreview.Items.Add(fileInfo.FullName, "", 0);
            item.Tag = fileInfo;
            item.SubItems.Add(fileInfo.Name);
            item.SubItems.Add("");
            item.SubItems.Add("");
        }

        private void AddWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            var fileList = e.Argument as IEnumerable<object>;
            var worker = sender as BackgroundWorker;

            if (fileList == null || worker == null) return;

            foreach (var file in fileList)
            {
                if (worker.CancellationPending)
                    break;

                var fileInfo = file as FileInfo;

                if (fileInfo == null && file is string)
                {
                    fileInfo = new FileInfo(file.ToString());
                }

                _renamer.CacheFile(fileInfo);
                worker.ReportProgress(0, fileInfo);
            }
        }

        private void AddWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            ToggleControls();
            UpdatePreview();
        }
        #endregion

        #region Private Types
        private enum LogType
        {
            Ok,
            Error,
            Warning
        }

        private sealed class LogMessage
        {
            public LogType Type { get; set; }
            public string Message { get; set; }
        }
        #endregion
    }
}
